package com.mezz.bones.framework.id.generator.service.impl;

import com.mezz.bones.framework.base.domain.enums.ResponseCode;
import com.mezz.bones.framework.base.exception.BaseException;
import com.mezz.bones.framework.cache.redis.service.RedisService;
import com.mezz.bones.framework.id.generator.domain.dto.IdRangeDto;
import com.mezz.bones.framework.id.generator.domain.entity.DistributedId;
import com.mezz.bones.framework.id.generator.repository.IDistributedIdRepository;
import com.mezz.bones.framework.id.generator.service.IDistributedIdGeneratorService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
@Slf4j
@AllArgsConstructor
public class DistributedIdGeneratorServiceImpl extends IDistributedIdGeneratorService {

    private static final long INCREMENT_STEP = 100;
    private static final String LOCK_PREFIX = "lock_distributed_id:";
    private static final long LOCK_WAIT_TIME = 3;  // 缩短等待时间
    private static final long LOCK_LEASE_TIME = 10; // 缩短租约时间

    private final IDistributedIdRepository distributedIdRepository;
    private final RedisService redisService;
    private final RedissonClient redissonClient;

    @Override
    public long getNextId(String businessKey) {
        String redisKey = "distributed_id:" + businessKey;

        // 1. 检查Redis是否已初始化
        Object currentValueObj = redisService.opsForValue().get(redisKey);
        if (currentValueObj == null) {
            // Redis未初始化，需要加锁初始化
            return initializeAndGetNextId(businessKey, redisKey);
        }

        // 2. Redis已初始化，直接原子性获取ID
        Long nextId = redisService.opsForValue().increment(redisKey, 1L);
        if (nextId == null) {
            throw new BaseException(ResponseCode.SYS_ERROR, "Redis操作失败");
        }

        // 3. 检查是否需要更新数据库步长（异步更新，不影响性能）
        if (nextId % INCREMENT_STEP == 0) {
            updateDatabaseStepAsync(businessKey, nextId);
        }

        return nextId;
    }

    @Override
    public IdRangeDto getNextIdRange(String businessKey, int batchSize) {
        if (batchSize <= 0) {
            throw new BaseException(ResponseCode.SYS_ERROR, "批量大小必须大于0");
        }
        if (batchSize > 10000) {
            throw new BaseException(ResponseCode.SYS_ERROR, "批量大小不能超过10000");
        }

        String redisKey = "distributed_id:" + businessKey;

        // 1. 先检查Redis是否已初始化
        Object currentValueObj = redisService.opsForValue().get(redisKey);
        if (currentValueObj == null) {
            return initializeAndGetIdRangeForBatch(businessKey, redisKey, batchSize);
        }

        // 2. 原子性批量获取ID
        Long endId = redisService.opsForValue().increment(redisKey, (long) batchSize);
        if (endId == null) {
            throw new BaseException(ResponseCode.SYS_ERROR, "Redis操作失败");
        }

        long startId = endId - batchSize + 1;

        // 3. 检查是否需要更新数据库步长
        if (endId / INCREMENT_STEP > (startId - 1) / INCREMENT_STEP) {
            updateDatabaseStepAsync(businessKey, endId);
        }

        log.debug("批量获取ID: businessKey={}, startId={}, endId={}, batchSize={}",
                businessKey, startId, endId, batchSize);

        return new IdRangeDto(startId, endId, batchSize);
    }

    /**
     * 初始化Redis缓存并获取下一个ID（单个ID获取场景）
     * 使用分布式锁确保初始化的原子性，避免与正常自增操作产生竞争
     */
    private long initializeAndGetNextId(String businessKey, String redisKey) {
        String lockKey = LOCK_PREFIX + businessKey + ":init";
        RLock lock = redissonClient.getLock(lockKey);

        try {
            if (lock.tryLock(LOCK_WAIT_TIME, LOCK_LEASE_TIME, TimeUnit.SECONDS)) {
                try {
                    // 双重检查，防止重复初始化
                    Object currentValueObj = redisService.opsForValue().get(redisKey);
                    if (currentValueObj != null) {
                        // 已经被其他线程初始化，释放锁后重新调用主方法
                        lock.unlock();
                        return getNextId(businessKey);
                    }

                    // 从数据库获取或创建初始值
                    DistributedId distributedId = getOrCreateDistributedId(businessKey);
                    long dbNextStartId = distributedId.getNextStartId();

                    // 设置Redis初始值为数据库值，然后原子性获取下一个ID
                    redisService.opsForValue().set(redisKey, dbNextStartId - 1);
                    Long nextId = redisService.opsForValue().increment(redisKey, 1L);

                    // 更新数据库
                    updateDatabaseNextStartId(distributedId, dbNextStartId);

                    log.info("初始化完成: businessKey={}, 返回ID={}, 数据库下次起始={}",
                            businessKey, nextId, dbNextStartId + INCREMENT_STEP);
                    return nextId;
                } finally {
                    if (lock.isHeldByCurrentThread()) {
                        lock.unlock();
                    }
                }
            } else {
                throw new BaseException(ResponseCode.SYS_ERROR, "获取初始化锁失败");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new BaseException(ResponseCode.SYS_ERROR, "线程中断");
        }
    }

    /**
     * 初始化Redis缓存并获取ID范围（批量ID获取场景）
     * 使用分布式锁确保初始化的原子性，避免与正常自增操作产生竞争
     */
    private IdRangeDto initializeAndGetIdRangeForBatch(String businessKey, String redisKey, int batchSize) {
        String lockKey = LOCK_PREFIX + businessKey + ":init";
        RLock lock = redissonClient.getLock(lockKey);

        try {
            if (lock.tryLock(LOCK_WAIT_TIME, LOCK_LEASE_TIME, TimeUnit.SECONDS)) {
                try {
                    // 双重检查，防止重复初始化
                    Object currentValueObj = redisService.opsForValue().get(redisKey);
                    if (currentValueObj != null) {
                        // 已经被其他线程初始化，释放锁后重新调用主方法
                        lock.unlock();
                        return getNextIdRange(businessKey, batchSize);
                    }

                    // 从数据库获取或创建初始值
                    DistributedId distributedId = getOrCreateDistributedId(businessKey);
                    long dbNextStartId = distributedId.getNextStartId();

                    // 设置Redis初始值，然后原子性获取批量ID
                    redisService.opsForValue().set(redisKey, dbNextStartId - 1);
                    Long endId = redisService.opsForValue().increment(redisKey, (long) batchSize);
                    long startId = endId - batchSize + 1;

                    // 更新数据库
                    updateDatabaseNextStartId(distributedId, dbNextStartId);

                    log.info("批量初始化完成: businessKey={}, startId={}, endId={}, 数据库下次起始={}",
                            businessKey, startId, endId, dbNextStartId + INCREMENT_STEP);

                    return new IdRangeDto(startId, endId, batchSize);
                } finally {
                    if (lock.isHeldByCurrentThread()) {
                        lock.unlock();
                    }
                }
            } else {
                throw new BaseException(ResponseCode.SYS_ERROR, "获取批量初始化锁失败");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new BaseException(ResponseCode.SYS_ERROR, "线程中断");
        }
    }

    /**
     * 异步更新数据库步长
     * 避免阻塞主流程，提升性能
     */
    private void updateDatabaseStepAsync(String businessKey, long currentId) {
        try {
            DistributedId distributedId = distributedIdRepository.findByBusinessKey(businessKey);
            if (distributedId != null) {
                updateDatabaseNextStartId(distributedId, currentId);
                log.debug("异步更新步长: businessKey={}, currentId={}, nextStartId={}",
                        businessKey, currentId, distributedId.getNextStartId());
            }
        } catch (Exception e) {
            // 异步更新失败不影响主流程，只记录日志
            log.warn("更新数据库步长失败: businessKey={}, currentId={}, error={}",
                    businessKey, currentId, e.getMessage());
        }
    }

    /**
     * 统一更新数据库nextStartId的方法
     * 确保数据库中的nextStartId始终是INCREMENT_STEP的整数倍
     *
     * @param distributedId 分布式ID实体
     * @param currentId 当前使用的ID值
     */
    private void updateDatabaseNextStartId(DistributedId distributedId, long currentId) {
        // 只有当currentId大于等于数据库中的nextStartId时才更新
        if (currentId >= distributedId.getNextStartId()) {
            // 计算下一个完整步长的起始位置
            // 例如：currentId=195，步长=100，则下一个步长起始位置应该是300
            long nextStepStartId = ((currentId / INCREMENT_STEP) + 1) * INCREMENT_STEP;
            distributedId.setNextStartId(nextStepStartId);
            distributedIdRepository.updateById(distributedId);
        }
    }

    /**
     * 获取或创建分布式ID记录
     */
    private DistributedId getOrCreateDistributedId(String businessKey) {
        DistributedId distributedId = distributedIdRepository.findByBusinessKey(businessKey);
        if (distributedId == null) {
            distributedId = new DistributedId();
            distributedId.setBusinessKey(businessKey);
            distributedId.setNextStartId(INCREMENT_STEP);
            distributedIdRepository.save(distributedId);
            log.debug("创建新记录: businessKey={}, nextStartId={}", businessKey, INCREMENT_STEP);
        }
        return distributedId;
    }
}